﻿/**
 * MassInterpolator
 * 
 * @author		Allen Chou
 * @version		1.0.0 (last update: Apr 7 2009)
 * @link        http://cjcat.blogspot.com
 * @link		http://cjcat2266.deviantart.com
 */

package idv.cjcat.utils {
	import flash.errors.*;
	
	import idv.cjcat.*;

	/**
	 * Linear interpolator for a series of vectors.
	 * <p>
	 * Let's say you have a series of two-dimensional vectors: (100, 0), (100, 100), (50, 100).
	 * The magnitude of the difference vector between the first two vectors is 100, and 50 for the last two.
	 * Summing up the magnitudes of all difference vectors of any consecutive two vectors in the series,
	 * you get the "total length" of this interpolator; in this example, the total length is 150.
	 * </p>
	 * <p>
	 * To interpolate, you provide an interpolation parameter <code>t</code>, which should be between zero and the total length.
	 * We define the "accumulated length (<code>AL</code>) of a vector <code>V</code> in the provided series" as "the sum of magnitudes of all difference vectors of all consecutive two vectors in the series from the beginning of the series up to vector <code>V</code>".
	 * If the interpolation parameter <code>t</code> lies between the accumulated length of vector <code>V</code>(inclusive) and <code>W</code>(exclusive),
	 * then we calculate the vector <code>X</code> such that <code>(V, AL[V])</code>, <code>(W, AL[W])</code>, and <code>(X, t)</code> lie on the same line,
	 * where X shall be our result of interpolation.
	 * </p>
	 */
	public class MassInterpolator {
		
		private var _segLengths:Vector.<Number> = new Vector.<Number>();
		private var _length:Number = 0;
		private var _data:Vector.<Number> = new Vector.<Number>();
		private var _numSegments:uint;
		
		private var _dimension:uint;
		
		/**
		 * Creates an interpolator.
		 * @param	dimension  Vector dimension.
		 */
		public function MassInterpolator(dimension:uint = 2) {
			this.dimension = dimension;
		}
		
		/**
		 * Interpolates the vectors.
		 * @param	t  The interpolation parameter.
		 * @param	usePercentage  Instead of absolute value, t is viewed as a percentage of value between zero and one.
		 * @return
		 */
		public function interpolate(t:Number, usePercentage:Boolean = false):Vector.<Number> {
			if (_data.length == 0) return null;
			
			if (usePercentage) {
				t = CJMath.clamp(t, 0, 1);
				t = t * _length;
			} else {
				t = CJMath.clamp(t, 0, _length);
			}
			
			var i:int;
			var result:Vector.<Number> = new Vector.<Number>();
			if (t >= _length) {
				for (i = 0; i < _dimension; i++) result.push(_data[_data.length - _dimension - 1 + i]);
				return result;
			}
			
			i = 0;
			var accumulation:Number = 0;
			while (true) {
				if ((accumulation <= t) && (accumulation + _segLengths[i] > t)) {
					for (var j:int = 0; j < _dimension; j++) {
						result.push(CJMath.interpolate(accumulation, _data[i * _dimension + j], accumulation + _segLengths[i], _data[(i + 1) * _dimension + j], t));
					}
					return result;
				}
				accumulation += _segLengths[i];
				i++;
			}
			
			return null;
		}
		
		/**
		 * Adds a vector to the interpolator series.
		 * <p>
		 * Each parameter passed is interpreted as a component of the vector, so pass a number of vectors equal to the dimension of the interpolator.
		 * </p>
		 * @param	...args
		 */
		public function push(...args):void {
			pushVec(Vector.<Number>(args));
		}
		
		public function setData(data:Vector.<Number>):void {
			if (data.length % _dimension != 0) throw new IllegalOperationError("Invalid data dimension.");
			
			var len:int = data.length / _dimension;
			var v:Vector.<Number> = new Vector.<Number>();
			for (var i:int = 0; i < len; i++) {
				v.length = 0;
				for (var j:int = 0; j < _dimension; j++) v.push(data[i * _dimension + j]);
				pushVec(v);
			}
		}
		
		public function pushVec(args:Vector.<Number>):void {
			if (args.length != _dimension) throw new IllegalOperationError("The number of arguments must equal to the interpolator's dimension.");
			for each (var num:Number in args) _data.push(num);
			
			if (_data.length == _dimension) {
				return;
			}
			
			var j:int = _data.length - _dimension;
			
			var sqSum:Number = 0, index:int, temp:Number;
			for (var i:int = 0; i < _dimension; i++) {
				index = i + j;
				temp = _data[index] - _data[index - _dimension];
				temp = temp * temp;
				sqSum += temp;
			}
			var len:Number = Math.sqrt(sqSum);
			_segLengths.push(len);
			_length += len;
		}
		
		/**
		 * Clears the interpolator of all vectors.
		 * @return
		 */
		public function clear():Vector.<Number> {
			var temp:Vector.<Number> = _data;
			_data = new Vector.<Number>();
			_segLengths.length = 0;
			_length = 0;
			return temp;
		}
		
		/**
		 * The total length of the interpolator.
		 */
		public function get length():Number { return _length; }
		/**
		 * Number of vectors in the series minus one.
		 */
		public function get numSegments():uint { return _numSegments; }
		
		/**
		 * The dimension of the interpolator.
		 */
		public function get dimension():uint { return _dimension; }
		public function set dimension(value:uint):void {
			_dimension = uint(CJMath.clamp(value, 1, uint.MAX_VALUE));
			_data.length = 0;
		}
		
		
	}
}